/*
 * $Id: OnWatchWakeupTask.java,v 1.13 2011/08/11 21:00:48 navdeep_mahajan Exp $
 * 
 * Copyright (C) 2006 General Electric Company. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of the General
 * Electric Company (GE). You shall not disclose this software and shall use it
 * only in accordance with the terms of the license agreement you entered into
 * with GE.
 * 
 * GE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
 * NON-INFRINGEMENT. GE SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING, OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES.
 */
package com.ge.healthcare.autosc.processor.dispatcher.tasks;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.ge.healthcare.autosc.common.ASCLogger;
import com.ge.healthcare.autosc.common.ASCUtil;
import com.ge.healthcare.autosc.common.commands.SSHCommand;
import com.ge.healthcare.autosc.common.commands.TelnetCommand;
import com.ge.healthcare.autosc.common.database.dao.IOnWatchSysHandShakeInfoDAO;
import com.ge.healthcare.autosc.common.database.dao.IRequestDAO;
import com.ge.healthcare.autosc.common.database.dao.IRequestLoginDetailsDAO;
import com.ge.healthcare.autosc.common.database.pojo.ActOnwatchSysHandShakeInfo;
import com.ge.healthcare.autosc.common.database.pojo.ActRequest;
import com.ge.healthcare.autosc.common.database.pojo.ActTask;
import com.ge.healthcare.autosc.common.exceptions.SSHCommandException;
import com.ge.healthcare.autosc.common.exceptions.TelnetCommandException;
import com.ge.healthcare.autosc.common.util.ASCIOHandler;
import com.ge.healthcare.autosc.common.util.ApplicationInfo;
import com.ge.healthcare.autosc.common.util.ConnectivityUtil;
import com.ge.healthcare.autosc.common.util.GSCCUtil;
import com.ge.healthcare.autosc.common.util.PropertyConstants;
import com.ge.healthcare.autosc.processor.exception.OnWatchException;
import com.ge.med.service.mc.SocketOpener;
import com.ge.med.service.mc.exceptions.MakeConnectionException;

/**
 * @author 212042764
 * 
 */
public class OnWatchWakeupTask extends BasicTask implements IOnWatchWakeup {

	private static final String MODEL_TYPE_PREFIX = "_";
	private ActRequest request = null;
	private ActTask task = null;
	private String defaultUser = null;
	private String password = null;
	private int defaultTimeoutMillis = -1;
	private String command = null;
	private SSHCommand sshCommand = null;
	private TelnetCommand telnetCommand = null;
	private boolean success = false;
	private String modelType = null;
	private boolean telnetAllowed = false;
	// Static Objects will be populated by spring on loading of application
	// context
	private static IRequestLoginDetailsDAO loginDetailsDAO = null;
	private static IRequestDAO requestDAO = null;
	private static IOnWatchSysHandShakeInfoDAO systemContactInfo = null;

	private static final String IIP_WAKEUP_SUCCESS = "IIP_WAKEUP_SUCCESS";

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.ge.healthcare.autosc.processor.dispatcher.tasks.BasicTask#executeTask
	 * (com.ge.healthcare.autosc.common.database.pojo.ActTask)
	 */
	@Override
	public void executeTask(ActTask actTask) {
		task = actTask;
		this.init();
		if (!hasSystemAlreadyConnected()) {
			try {
				this.makeConnection();
				this.initLoginDetails();
				if (isSSHPortOpen()) {
					this.executeWakeUp();
				} else {
					this.executeWakeUpOnTelnet();
				}

			} finally {
				this.addChronology();
				this.cleanup();
			}
		} else {
			success = true;
		}
		this.endLogProcessing();
	}

	/**
	 * Initializes requires variables
	 */
	private void init() {
		request = requestDAO.getByRequestID(task.getRequestId());
		sshCommand = new SSHCommand();
		defaultTimeoutMillis = ApplicationInfo.getInstance()
				.getIntegerPropertyValue(
						PropertyConstants.ONWATCH_WAKEUP_DEFAULT_TIMEOUT,
						"60000");
		command = ApplicationInfo.getInstance().getProperty(
				PropertyConstants.ONWATCH_WAKEUP_DEFAULT_COMMAND,
				"~insite/bin/iip_wakeup");
		modelType = request.getModelType();
		if (modelType.startsWith(MODEL_TYPE_PREFIX)) {
			modelType = modelType.substring(MODEL_TYPE_PREFIX.length());
		}
		ASCLogger.info(this.getClass(), "init", "Model type is:" + modelType);
		if (ASCUtil.containsInProperty(
				PropertyConstants.MODEL_TYPES_TO_TELNET_FOR, ",", modelType)) {
			telnetAllowed = true;
		}
		ASCLogger.info(this.getClass(), "init", "telnetAllowed:"
				+ telnetAllowed);
	}

	/**
	 * Checks to see if the system has already contacted after the task was
	 * created. If the system has already contacted we don't need to invoke
	 * onWatch Wakeup
	 * 
	 * @return
	 */
	public boolean hasSystemAlreadyConnected() {
		String methodName = "hasSystemAlreadyConnected";
		ActOnwatchSysHandShakeInfo contactInfo = systemContactInfo
				.getSysContactInfoBySystemId(request.getSystemId());
		if (!ASCUtil.isNullOrEmptyObject(contactInfo)
				&& contactInfo.getLastContactTime().after(task.getStartTime())) {
			StringBuffer message = new StringBuffer().append(
					"System has already contacted, contact Info:").append(
					contactInfo).append(", Task Details:").append(task);
			ASCLogger.info(this.getClass(), methodName, message, true);
			return true;
		}
		return false;
	}

	/**
	 * Brings remote device to 3. domain
	 */
	private void makeConnection() {
		try {
			ConnectivityUtil.makeConnection(request.getSystemId(), this
					.getClass().getName(), 1, null);
		} catch (MakeConnectionException mcException) {
			String message = "Make Connection call failed for systemId:"
					+ request.getSystemId();
			throw new OnWatchException(message, mcException, PRC_ONW_F_MCF_001);
		}
	}

	/**
	 * Performs login using insite
	 */
	private void performLogin() {
		try {
			sshCommand.connect(request.getIpAddress(), defaultUser, password,
					defaultTimeoutMillis);
			ASCLogger.info(this.getClass(), "performLogin", new StringBuffer()
					.append("Successful ssh login to:").append(
							request.getIpAddress())
					.append("  using user name:").append(defaultUser), true);
		} catch (SSHCommandException sshException) {
			throw new OnWatchException("SSH " + this.getFailedLoginMessage(),
					sshException, PRC_ONW_F_CON_001);
		}
	}

	/**
	 * Message to log if login fails
	 * 
	 * @param login
	 * @param passcode
	 * @return
	 */
	private String getFailedLoginMessage() {
		return new StringBuffer().append("Login failed for ip address:")
				.append(request.getIpAddress()).append(" Request Id:").append(
						request.getRequestId()).append(" using login name:")
				.append(defaultUser).toString();
	}

	/**
	 * Initializes login details
	 */
	private void initLoginDetails() {
		defaultUser = ApplicationInfo.getInstance().getProperty(
				PropertyConstants.ONWATCH_WAKEUP_DEFAULT_USER, "insite");
		password = loginDetailsDAO.getLoginDetailsByRequestIDAndUser(task
				.getRequestId(), defaultUser);
		if (ASCUtil.isNullOrEmpty(password)) {
			throw new OnWatchException(new StringBuffer().append(
					"No passcode specified for user:").append(defaultUser)
					.append(" for request id:").append(request.getRequestId())
					.toString(), PRC_ONW_F_VAL_001);
		}
		password = password.trim();
	}

	/**
	 * Executes wakeup command
	 */
	private void executeWakeUp() {
		this.performLogin();
		try {
			sshCommand.executeCommand(command);
			List<String> response = sshCommand.getResponse();
			if (ASCUtil.isNullOrEmptyList(response)) {
				throw new OnWatchException("No response received for command:"
						+ command, PRC_ONW_F_RES_001);
			}
			boolean success = false;
			for (String responseLine : response){
				if (!ASCUtil.isNullOrEmpty(responseLine) && responseLine.indexOf(IIP_WAKEUP_SUCCESS) != -1) {
					success = true;
					break;
				}
			}
			if (!success) {
				throw new OnWatchException(
						"On Watch wakeup invocation failed, received response:"
								+ response, PRC_ONW_F_RES_002);
			}
			this.success = true;
		} catch (SSHCommandException sshException) {
			throw new OnWatchException("Failed to execute command:" + command,
					sshException, PRC_ONW_F_EXC_001);
		}
	}

	/**
	 * The method connects to remote device on telnet
	 */
	private void performLoginUsingTelnet() {
		try {
			telnetCommand = new TelnetCommand(modelType);
			telnetCommand.setInsitePrompt();
			telnetCommand.connect(request.getIpAddress(), defaultUser,
					password, defaultTimeoutMillis);
			ASCLogger.info(this.getClass(), "performLoginUsingTelnet",
					new StringBuffer().append("Successful telnet login to:")
							.append(request.getIpAddress()).append(
									"  using user name:").append(defaultUser),
					true);
		} catch (Exception exception) {
			throw new OnWatchException(
					"Telnet " + this.getFailedLoginMessage(), exception,
					PRC_ONW_F_CON_001);
		}
	}

	/**
	 * The method tries and invokes wakeup on telnet
	 */
	private void executeWakeUpOnTelnet() {
		if (!telnetAllowed) {
			throw new OnWatchException("SSH " + this.getFailedLoginMessage(),
					PRC_ONW_F_CON_001);
		}
		this.performLoginUsingTelnet();
		try {
			String response = telnetCommand.executeCommand(command);
			if (ASCUtil.isNullOrEmpty(response)) {
				throw new OnWatchException(
						"No response received using telnet for command:"
								+ command, PRC_ONW_F_RES_001);
			}

			if (response.indexOf(IIP_WAKEUP_SUCCESS) == -1) {
				throw new OnWatchException(
						"On Watch wakeup invocation failed on telnet, received response:"
								+ response, PRC_ONW_F_RES_002);
			}
			this.success = true;
		} catch (TelnetCommandException telnetCommandException) {
			throw new OnWatchException("Failed to execute command on telnet:"
					+ command, telnetCommandException, PRC_ONW_F_EXC_001);
		}
	}

	/**
	 * Performs cleanup after task execution
	 */
	private void cleanup() {
		if (!ASCUtil.isNullOrEmptyObject(sshCommand)) {
			sshCommand.disconnect();
		}
		if (!ASCUtil.isNullOrEmptyObject(telnetCommand)) {
			telnetCommand.disconnect();
		}
	}

	/**
	 * Adds chronology entry. This is need for customer connectivity information
	 */
	protected void addChronology() {
		Map<String, String> chronologyMap = new HashMap<String, String>();
		chronologyMap.put(GSCCUtil.APPLICATION, "AutoSC");
		chronologyMap.put(GSCCUtil.ATTEMPT_NUM, (task.getFailedCount() + 1)
				+ "");
		String completionCode = "Failure";
		if (success) {
			completionCode = "Success";
		}
		chronologyMap.put(GSCCUtil.COMPLETION_CODE, completionCode);
		chronologyMap.put(GSCCUtil.COMMENT, "OnWatch wakeup task "
				+ completionCode);
		chronologyMap.put(GSCCUtil.LOGIN_ID, "asc_acct");
		chronologyMap.put(GSCCUtil.PLATFORM, "OnWatchWakeup");
		chronologyMap.put(GSCCUtil.SYSTEM_ID, request.getSystemId());
		chronologyMap.put(GSCCUtil.TYPE_ID, "Outbound");
		GSCCUtil.addChronology(chronologyMap);
	}

	/**
	 * Logs the final status of the transaction
	 * 
	 * @param success
	 */
	protected void endLogProcessing() {
		String methodName = "endProcessing";
		String logMessage = "  !!!! FAILED !!!";
		if (success) {
			logMessage = "  !!!! SUCCESS !!!";
		}
		ASCLogger.info(this.getClass(), methodName, new StringBuffer().append(
				logMessage).append(" for task:").append(task), true);
	}

	/**
	 * The method checks to see if telnet port is open
	 * 
	 * @return
	 */
	private boolean isSSHPortOpen() {
		Socket socketHost = null;
		BufferedReader in = null;
		boolean result = false;
		socketHost = SocketOpener.openSocket(request.getIpAddress(), 22,
				defaultTimeoutMillis);
		if (socketHost != null) {
			try {
				in = new BufferedReader(new InputStreamReader(socketHost
						.getInputStream()));
				result = true;
			} catch (IOException e) {
				ASCLogger.info(this.getClass(), "isSSHPortOpen",
						"SSH Port is not reachable", true);
			} finally {
				ASCIOHandler.closeReader(in);
				closeSocket(socketHost);
			}
		}
		return result;
	}

	/**
	 * Closes Socket objects, in it can't close them it nullifies them
	 * 
	 * @param socketHost
	 */
	private static void closeSocket(Socket socketHost) {
		if (socketHost != null) {
			try {
				socketHost.close();
			} catch (IOException e) {
				socketHost = null;
			}
		}
	}

	/**
	 * @param requestDAO
	 *            the requestDAO to set
	 */
	public void setRequestDAO(IRequestDAO requestDAO) {
		OnWatchWakeupTask.requestDAO = requestDAO;
	}

	/**
	 * @param loginDetailsDAO
	 *            the loginDetailsDAO to set
	 */
	public void setLoginDetailsDAO(IRequestLoginDetailsDAO loginDetailsDAO) {
		OnWatchWakeupTask.loginDetailsDAO = loginDetailsDAO;
	}

	/**
	 * @param systemContactInfo
	 *            the systemContactInfo to set
	 */
	public void setSystemContactInfo(
			IOnWatchSysHandShakeInfoDAO systemContactInfo) {
		OnWatchWakeupTask.systemContactInfo = systemContactInfo;
	}

}
